use crate::ffi::*;
use std::ffi::{CString, CStr};
// use libc::{c_void, size_t, c_int, c_char, ptrdiff_t, c_double};
use libc::c_void;

pub struct LuaEngine {
    state: *mut lua_State,
    valid: bool,
    debug_stack: bool
}

impl LuaEngine {
    pub fn new() -> Self {
        unsafe {
            let state = luaL_newstate();
            luaL_openlibs(state);
            LuaEngine { state: state, valid: true, debug_stack: false }
        }
    }

    fn is_valid(&self) -> bool {
        return self.valid;
    }
    fn is_debug_stack(&self) -> bool {
        return self.debug_stack;
    }
    fn debug_dump(&self) {
        if self.is_debug_stack() {
            self.dump_stack();
        }
    }

    pub fn pop_stack(&self, index: &i32) {
        unsafe {
            lua_settop(self.state, *index);
            self.debug_dump();
        }
    }

    pub fn dump_stack(&self) {
        if !self.is_valid() {
            println!("Invalid");
            return;
        }

        unsafe {
            let stack_top = lua_gettop(self.state);

            for i in 1..=stack_top {
                let element_type = lua_type(self.state, i);
                let type_str;
                let output;
                match element_type {
                    0 => {
                        output = CString::new("nil").unwrap();
                        type_str = String::from("nil            ");
                    },
                    1 => {
                        output = CString::new(lua_toboolean(self.state, i).to_string()).unwrap();
                        type_str = String::from("boolean        ");
                    },
                    2 => {
                        output = CString::new((lua_touserdata(self.state, i) as usize).to_string()).unwrap();
                        type_str = String::from("lightuserdata  ");
                    },
                    3 => {
                        let is_integer = lua_isinteger(self.state, i) != 0;
                        if is_integer {
                            output = CString::new(lua_tointegerx(self.state, i, std::ptr::null_mut()).to_string() + &is_integer.to_string()).unwrap();
                        } else {
                            output = CString::new(lua_tonumberx(self.state, i, std::ptr::null_mut()).to_string() + &is_integer.to_string()).unwrap();
                        }
                        type_str = String::from("number         ");
                    },
                    4 => {
                        output = CStr::from_ptr(lua_tolstring(self.state, i, std::ptr::null_mut())).to_owned();
                        type_str = String::from("string         ");
                    },
                    5 => {
                        output = CString::new("todo").unwrap();
                        type_str = String::from("table          ");
                    },
                    6 => {
                        output = CString::new("todo").unwrap();
                        type_str = String::from("function       ");
                    },
                    7 => {
                        output = CString::new((lua_touserdata(self.state, i) as usize).to_string()).unwrap();
                        type_str = String::from("userdata       ");
                    },
                    8 => {
                        output = CString::new("todo").unwrap();
                        type_str = String::from("thread         ");
                    },
                    _ => {
                        output = CString::new("unknown").unwrap();
                        type_str = String::from("unknown        ");
                    }
                }
                println!("Stack Index {}[{:?}]: {:?}", i, type_str, output);
            }
            println!("");
        }
    }

    pub fn set_global_integer(&self, name: &str, value: &lua_Integer) {
        unsafe {
            lua_pushinteger(self.state, *value);
            self.debug_dump();

            let c_string = CString::new(name).unwrap();
            lua_setglobal(self.state, c_string.as_ptr());
            self.debug_dump();
        }
    }

    pub fn set_global_value(&self, name: &str, index: &i32) {
        unsafe {
            if *index != 0 {
                lua_pushvalue(self.state, *index);
                self.debug_dump();
            }

            let c_string = CString::new(name).unwrap();
            lua_setglobal(self.state, c_string.as_ptr());
            self.debug_dump();

            if *index != 0 {
                lua_settop(self.state, -2);
                self.debug_dump();
            }
        }
    }

    pub fn create_table(&self) {
        unsafe{
            lua_createtable(self.state, 0, 0);
            self.debug_dump();
        }
    }

    pub fn create_metatable(&self, name: &str) {
        unsafe{
            let c_string = CString::new(name).unwrap();
            luaL_newmetatable(self.state, c_string.as_ptr());
            self.debug_dump();
        }
    }

    /// Set Table[name] = value
    /// if index == 0, make sure stack top is a table
    pub fn set_table_integer(&self, name: &str, value: &lua_Integer, index: &i32) {
        unsafe {
            if *index != 0 {
                lua_pushvalue(self.state, *index);
                self.debug_dump();
            }
            
            lua_pushinteger(self.state, *value);
            self.debug_dump();

            let c_string = CString::new(name).expect("");
            lua_setfield(self.state, -2, c_string.as_ptr());
            self.debug_dump();

            if *index != 0 {
                lua_settop(self.state, -2);
                self.debug_dump();
            }
        }
    }

    /// Set Table[name] = value
    /// if index == 0, make sure stack top is a table
    pub fn set_table_cfunction(&self, name: &str, value: &lua_CFunction, index: &i32) {
        unsafe {
            if *index != 0 {
                lua_pushvalue(self.state, *index);
                self.debug_dump();
            }
            
            lua_pushcclosure(self.state, *value, 0);
            self.debug_dump();

            let c_string = CString::new(name).expect("");
            lua_setfield(self.state, -2, c_string.as_ptr());
            self.debug_dump();

            if *index != 0 {
                lua_settop(self.state, -2);
                self.debug_dump();
            }
        }
    }

    /// Set Table.metatable = METATABLE[name]
    /// if index == 0, make sure stack top is a table
    pub fn set_metatable(&self, name: &str, index: &i32) {
        unsafe {
            if *index != 0 {
                lua_pushvalue(self.state, *index);
                self.debug_dump();
            }

            let c_string = CString::new(name).expect("");
            luaL_setmetatable(self.state, c_string.as_ptr());
            self.debug_dump();

            if *index != 0 {
                lua_settop(self.state, -2);
                self.debug_dump();
            }
        }
    }

    pub fn push_lightuserdata(&self, ptr: *mut c_void) {
        unsafe {
            lua_pushlightuserdata(self.state, ptr);
            self.debug_dump();
        }
    } 

    pub fn run_script(&self, script: &CString) {
        unsafe {
            luaL_dostring(self.state, script.as_ptr());
            self.debug_dump();
        }
    }
}